package com.songyang.yougnsql.core.table;

import com.songyang.yougnsql.common.ErrorMsg;
import com.songyang.yougnsql.common.ExceptionHandler;
import com.songyang.yougnsql.common.YoungSQLException;
import com.songyang.yougnsql.core.data.DataManager;
import com.songyang.yougnsql.core.parser.statement.*;
import com.songyang.yougnsql.core.table.res.BeginRes;
import com.songyang.yougnsql.core.utils.ByteUtils;
import com.songyang.yougnsql.core.version.Transaction;
import com.songyang.yougnsql.core.version.VersionManager;

import java.util.*;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

/**
 * @ClassName TableManagerImpl
 * @Description 表管理的实现类
 * @date 2022/12/29 10:22
 * @Author yanceysong
 * @Version 1.0
 */
public class TableManagerImpl implements TableManager {
    private final BootStrap bootStrap;
    /**
     * 缓存，表明和表的实体类
     */
    private final Map<String, Table> tableCache;
    /**
     * 缓存，xid下面的表
     */
    private final Map<Long, List<Table>> xidTableCache;
    private final Lock lock;
    public VersionManager versionManager;
    public DataManager dataManager;

    TableManagerImpl(VersionManager vm, DataManager dm, BootStrap bootStrap) {
        this.versionManager = vm;
        this.dataManager = dm;
        this.bootStrap = bootStrap;
        this.tableCache = new HashMap<>();
        this.xidTableCache = new HashMap<>();
        lock = new ReentrantLock();
        //加载所有表信息
        loadTables();
    }

    /**
     * 加载所有的表信息
     */
    private void loadTables() {
        //拿到第一个表的uid数据
        long uid = firstTableUid();
        //从表的兄弟节点一直遍历加载，直到加载完所有的表信息
        while (uid != 0) {
            Table tb = Table.loadTable(this, uid);
            assert tb != null;
            uid = tb.nextUid;
            tableCache.put(tb.name, tb);
        }
    }

    /**
     * 返回第一张表的uid
     *
     * @return uid
     */
    private long firstTableUid() {
        return ByteUtils.parseLong(bootStrap.load());
    }

    /**
     * 更新第一张表的uid
     *
     * @param uid 待更新的uid
     */
    private void updateFirstTableUid(long uid) {
        bootStrap.update(ByteUtils.long2Byte(uid));
    }

    @Override
    public BeginRes begin(Begin begin) {
        BeginRes res = new BeginRes();
        int level = begin.isRepeatableRead ? Transaction.REPEATABLE_READ : Transaction.READ_COMMITTED;
        res.xid = versionManager.begin(level);
        res.result = "begin " + res.xid;
        return res;
    }

    /**
     * 提交一个事务
     *
     * @param xid 事务的id
     * @return 结果
     * @throws Exception 异常
     */
    @Override
    public String commit(long xid) throws Exception {
        List<Table> tables = xidTableCache.get(xid);
        if (tables != null) {
            for (Table t : tables) {
                tableCache.put(t.name, t);
            }
            versionManager.commit(xid);
        }
        return "commit";
    }

    /**
     * 回滚一个事务
     *
     * @param xid 事务的id
     * @return 结果
     */
    @Override
    public String abort(long xid) {
        //如果有表就删除所有
        List<Table> tables = xidTableCache.get(xid);
        if (tables != null) {
            for (Table t : tables) {
                try {
                    versionManager.delete(xid, t.uid);
                } catch (Exception e) {
                    ExceptionHandler.handler(e);
                }
            }
        }
        versionManager.abort(xid);
        return "abort "+xid;
    }

    /**
     * show
     *
     * @param xid 事务的id
     * @return 结果
     */
    @Override
    public String show(long xid) {
        lock.lock();
        try {
            StringJoiner joiner = new StringJoiner("\n");
            //已有的表
            for (Table tb : tableCache.values()) {
                joiner.add(tb.toString());
            }
            List<Table> t = xidTableCache.get(xid);
            if (t == null) {
                return joiner.length() < 1 ? "no tables exist!" : joiner.toString();
            }
            //自己刚刚创建的表
            for (Table tb : t) {
                joiner.add(tb.toString());
            }
            return joiner.length() < 1 ? "no tables exist!" : joiner.toString();
        } finally {
            lock.unlock();
        }
    }

    /**
     * 创建表
     *
     * @param xid    事务的id
     * @param create 创建
     * @return 创建的结果
     * @throws Exception 异常
     */
    @Override
    public String create(long xid, Create create) throws Exception {
        lock.lock();
        try {
            if (tableCache.containsKey(create.tableName)) {
                throw new YoungSQLException(ErrorMsg.DUPLICATED_TABLE_EXCEPTION);
            }
            Table table = Table.createTable(this, firstTableUid(), xid, create);
            updateFirstTableUid(table.uid);
//            tableCache.put(create.tableName, table);
            if (!xidTableCache.containsKey(xid)) {
                xidTableCache.put(xid, new ArrayList<>());
            }
            xidTableCache.get(xid).add(table);
            return ("create " + create.tableName);
        } finally {
            lock.unlock();
        }
    }

    /**
     * 插入表的操作
     *
     * @param xid    事务的id
     * @param insert 插入的抽象
     * @return 插入的结果
     * @throws Exception 异常
     */
    @Override
    public String insert(long xid, Insert insert) throws Exception {
        lock.lock();
        Table table = tableCache.containsKey(insert.tableName) ?
                tableCache.get(insert.tableName) : getUnCommitTable(xid, insert.tableName);
        lock.unlock();
        if (table == null && !tableIsUnCommit(xid, insert.tableName)) {
            throw new YoungSQLException(ErrorMsg.TABLE_NOT_FOUND_EXCEPTION);
        }
        assert table != null;
        table.insert(xid, insert);
        return "insert "+insert.tableName;
    }

    /**
     * 在没有提交的事务创建表中获取一个表
     *
     * @param xid       事务的id
     * @param tableName 表名字
     * @return 表
     */
    private Table getUnCommitTable(long xid, String tableName) {
        List<Table> tables = xidTableCache.get(xid);
        if (tables == null) return null;
        for (Table t : tables) {
            if (t.name.equals(tableName)) return t;
        }
        return null;
    }

    /**
     * 查询表操作
     *
     * @param xid    事务的id
     * @param select 查询的抽象
     * @return 查询的结果
     * @throws Exception 异常
     */
    @Override
    public String read(long xid, Select select) throws Exception {
        lock.lock();
        Table table = tableCache.containsKey(select.tableName) ?
                tableCache.get(select.tableName) : getUnCommitTable(xid, select.tableName);
        lock.unlock();
        if (table == null && !tableIsUnCommit(xid, select.tableName)) {
            throw new YoungSQLException(ErrorMsg.TABLE_NOT_FOUND_EXCEPTION);
        }
        assert table != null;
        return table.read(xid, select);
    }

    /**
     * 判断某个事物的某个表是否已经提交
     *
     * @param xid       事物的id
     * @param tableName 表名
     * @return 结果
     */
    private boolean tableIsUnCommit(long xid, String tableName) {
        return xidTableCache.get(xid).stream().anyMatch(table -> table.name.equals(tableName));
    }

    /**
     * 更新表操作
     *
     * @param xid    事务的id
     * @param update 更新的抽象
     * @return 执行的结果，展示给客户端
     * @throws Exception 异常
     */
    @Override
    public String update(long xid, Update update) throws Exception {
        lock.lock();
        Table table = tableCache.containsKey(update.tableName) ?
                tableCache.get(update.tableName) : getUnCommitTable(xid, update.tableName);
        lock.unlock();
        if (table == null && !tableIsUnCommit(xid, update.tableName)) {
            throw new YoungSQLException(ErrorMsg.TABLE_NOT_FOUND_EXCEPTION);
        }
        int count = table.update(xid, update);
        return ("update " + count);
    }

    /**
     * 删除表操作
     *
     * @param xid    事务的id
     * @param delete 删除的抽象
     * @return 执行的结果，展示给客户端
     * @throws Exception 异常
     */
    @Override
    public String delete(long xid, Delete delete) throws Exception {
        lock.lock();
        Table table = tableCache.get(delete.tableName);
        lock.unlock();
        if (table == null) {
            throw new YoungSQLException(ErrorMsg.TABLE_NOT_FOUND_EXCEPTION);
        }
        int count = table.delete(xid, delete);
        return ("delete " + count);
    }
}
